"""
推荐系统核心模块
包含基于用户的协同过滤和基于物品的协同过滤
"""
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sqlalchemy.orm import Session
from sqlalchemy import func, desc
from typing import List, Dict, Tuple, Optional
from datetime import datetime, timedelta
import logging
import random

from Models.travel.models import UserBehavior, Attraction, UserSegment, Favorite, Order, Review

logger = logging.getLogger(__name__)


class UserBasedCF:
    """基于用户的协同过滤推荐算法"""
    
    def __init__(self, db: Session):
        self.db = db
        self.rating_matrix = None
        self.users = None
        self.items = None
        self.user_similarity = None
        self.user_to_similar_users = None
    
    def load_data(self) -> pd.DataFrame:
        """从数据库加载用户行为数据"""
        try:
            # 获取用户行为数据（最近30天）
            cutoff_date = datetime.now() - timedelta(days=30)
            behaviors = self.db.query(UserBehavior).filter(
                UserBehavior.created_at >= cutoff_date
            ).all()
            
            if not behaviors:
                logger.warning("没有找到用户行为数据")
                return pd.DataFrame()
            
            # 转换为DataFrame
            data = []
            for behavior in behaviors:
                data.append({
                    "user_id": behavior.user_id,
                    "item_id": behavior.attraction_id,
                    "time": behavior.created_at.strftime("%Y-%m-%d"),
                    "rating": behavior.score
                })
            
            df = pd.DataFrame(data, columns=["user_id", "item_id", "time", "rating"])
            logger.info(f"加载了 {len(df)} 条用户行为数据")
            return df
        
        except Exception as e:
            logger.error(f"加载数据失败: {e}", exc_info=True)
            return pd.DataFrame()
    
    def build_rating_matrix(self, df: pd.DataFrame):
        """构建用户-物品评分矩阵"""
        if df.empty:
            logger.warning("数据为空，无法构建评分矩阵")
            return
        
        self.users = df["user_id"].unique()
        self.items = df["item_id"].unique()
        
        # 初始化矩阵
        self.rating_matrix = np.zeros((len(self.users), len(self.items)))
        
        # 填充矩阵
        for user_idx, user in enumerate(self.users):
            for item_idx, item in enumerate(self.items):
                rating = df[
                    (df["user_id"] == user) & (df["item_id"] == item)
                ]["rating"].values
                self.rating_matrix[user_idx, item_idx] = rating[0] if len(rating) > 0 else 0
        
        logger.info(f"评分矩阵构建完成，维度: {self.rating_matrix.shape}")
    
    def compute_user_similarity(self, k_similar_users: int = 2):
        """计算用户相似度并构建索引"""
        if self.rating_matrix is None:
            logger.error("评分矩阵未构建，无法计算相似度")
            return
        
        # 计算余弦相似度
        self.user_similarity = cosine_similarity(self.rating_matrix)
        
        # 构建相似用户索引
        self.user_to_similar_users = {}
        for user_idx, user in enumerate(self.users):
            similarity_scores = self.user_similarity[user_idx]
            # 排序并取Top-k相似用户（排除自身）
            similar_user_indices = np.argsort(similarity_scores)[::-1][1:k_similar_users + 1]
            
            similar_users = [
                (int(self.users[idx]), float(similarity_scores[idx]))
                for idx in similar_user_indices
            ]
            self.user_to_similar_users[int(user)] = similar_users
        
        logger.info(f"用户相似度索引构建完成，共 {len(self.users)} 个用户")
    
    def recommend(self, user_id: int, top_n: int = 3) -> List[Tuple[int, float]]:
        """
        为指定用户推荐物品
        
        Args:
            user_id: 用户ID
            top_n: 推荐数量
            
        Returns:
            List[Tuple[item_id, score]]: 推荐物品列表（物品ID，推荐得分）
        """
        if self.rating_matrix is None or self.user_to_similar_users is None:
            logger.error("推荐系统未初始化")
            return []
        
        # 检查用户是否存在
        if user_id not in self.users:
            logger.warning(f"用户 {user_id} 不在数据集中")
            return []
        
        # 1. 找到目标用户未交互的物品
        target_user_idx = np.where(self.users == user_id)[0][0]
        target_user_ratings = self.rating_matrix[target_user_idx]
        unrated_item_indices = np.where(target_user_ratings == 0)[0]
        unrated_items = self.items[unrated_item_indices]
        
        if not unrated_items.size:
            logger.info(f"用户 {user_id} 已交互所有物品，无推荐内容")
            return []
        
        # 2. 获取相似用户
        similar_users = self.user_to_similar_users.get(user_id, [])
        if not similar_users:
            logger.warning(f"未找到与用户 {user_id} 相似的用户")
            return []
        
        # 3. 计算加权推荐分数
        item_scores = {}
        for sim_user_id, sim_score in similar_users:
            sim_user_idx = np.where(self.users == sim_user_id)[0][0]
            sim_user_ratings = self.rating_matrix[sim_user_idx]
            
            for item in unrated_items:
                item_global_idx = np.where(self.items == item)[0][0]
                item_rating = sim_user_ratings[item_global_idx]
                if item_rating > 0:
                    weighted_score = sim_score * item_rating
                    if item not in item_scores:
                        item_scores[item] = 0
                    item_scores[item] += weighted_score
        
        # 4. 排序并返回结果
        sorted_items = sorted(
            item_scores.items(),
            key=lambda x: x[1],
            reverse=True
        )[:top_n]
        
        return [(int(item_id), float(score)) for item_id, score in sorted_items]
    
    def initialize(self, k_similar_users: int = 2):
        """初始化推荐系统"""
        df = self.load_data()
        if not df.empty:
            self.build_rating_matrix(df)
            self.compute_user_similarity(k_similar_users)
            return True
        return False


class ItemBasedCF:
    """基于物品的协同过滤推荐算法"""
    
    def __init__(self, db: Session):
        self.db = db
        self.user_to_items = {}
        self.user_rated_items = {}
        self.item_to_similar = {}
        self.items = None
        self.users = None
        self.rating_matrix = None
    
    def load_data(self) -> pd.DataFrame:
        """从数据库加载用户行为数据"""
        try:
            cutoff_date = datetime.now() - timedelta(days=30)
            behaviors = self.db.query(UserBehavior).filter(
                UserBehavior.created_at >= cutoff_date
            ).all()
            
            if not behaviors:
                logger.warning("没有找到用户行为数据")
                return pd.DataFrame()
            
            data = []
            for behavior in behaviors:
                data.append({
                    "user_id": behavior.user_id,
                    "item_id": behavior.attraction_id,
                    "time": behavior.created_at.strftime("%Y-%m-%d"),
                    "rating": behavior.score
                })
            
            df = pd.DataFrame(data, columns=["user_id", "item_id", "time", "rating"])
            logger.info(f"加载了 {len(df)} 条用户行为数据")
            return df
        
        except Exception as e:
            logger.error(f"加载数据失败: {e}", exc_info=True)
            return pd.DataFrame()
    
    def build_user_item_index(self, df: pd.DataFrame):
        """构建用户→物品索引（带评分）"""
        if df.empty:
            return
        
        # 按用户分组，按时间排序
        for user, group in df.groupby("user_id"):
            sorted_group = group.sort_values("time", ascending=False)
            item_info = list(zip(
                sorted_group["item_id"],
                sorted_group["rating"],
                sorted_group["time"]
            ))
            self.user_to_items[int(user)] = item_info
        
        # 构建用户已评价物品的集合索引
        self.user_rated_items = {
            user: {int(item_id) for item_id, _, _ in items}
            for user, items in self.user_to_items.items()
        }
        
        logger.info(f"用户-物品索引构建完成，共 {len(self.user_to_items)} 个用户")
    
    def build_item_similarity_index(self, df: pd.DataFrame, k_similar: int = 2):
        """构建物品→物品索引（带相似度得分）"""
        if df.empty:
            return
        
        self.users = df["user_id"].unique()
        self.items = df["item_id"].unique()
        
        # 构建评分矩阵
        self.rating_matrix = np.zeros((len(self.users), len(self.items)))
        
        for user_idx, user in enumerate(self.users):
            for item_idx, item in enumerate(self.items):
                rating = df[
                    (df["user_id"] == user) & (df["item_id"] == item)
                ]["rating"].values
                self.rating_matrix[user_idx, item_idx] = rating[0] if len(rating) > 0 else 0
        
        # 计算物品相似度（转置矩阵）
        item_similarity = cosine_similarity(self.rating_matrix.T)
        
        # 构建相似物品索引
        self.item_to_similar = {}
        for item_idx, item in enumerate(self.items):
            similarity_scores = item_similarity[item_idx]
            similar_indices = np.argsort(similarity_scores)[::-1][1:k_similar + 1]
            similar_items = [
                (int(self.items[idx]), float(similarity_scores[idx]))
                for idx in similar_indices
            ]
            self.item_to_similar[int(item)] = similar_items
        
        logger.info(f"物品相似度索引构建完成，共 {len(self.items)} 个物品")
    
    def recommend(self, user_id: int, top_n: int = 3) -> List[Tuple[int, float]]:
        """
        为指定用户推荐物品
        
        Args:
            user_id: 用户ID
            top_n: 推荐数量
            
        Returns:
            List[Tuple[item_id, score]]: 推荐物品列表
        """
        # 1. 获取用户近期交互物品
        user_interactions = self.user_to_items.get(user_id, [])
        if not user_interactions:
            logger.info(f"用户 {user_id} 无交互记录，无法推荐")
            return []
        
        # 获取用户已评价过的物品集合
        rated_items = self.user_rated_items.get(user_id, set())
        
        # 2. 收集所有相似物品（带相似度得分）
        candidate_items = {}
        for item_id, rating, _ in user_interactions:
            similar_items = self.item_to_similar.get(int(item_id), [])
            for sim_item_id, sim_score in similar_items:
                # 过滤掉用户已经评价过的物品
                if sim_item_id in rated_items:
                    continue
                
                # 简单加权：相似度 × 用户对原物品的评分
                weighted_score = sim_score * rating
                
                # 去重：取最高得分
                if sim_item_id not in candidate_items or weighted_score > candidate_items[sim_item_id]:
                    candidate_items[sim_item_id] = weighted_score
        
        # 3. 按加权得分排序，取Top-N
        sorted_candidates = sorted(
            candidate_items.items(),
            key=lambda x: x[1],
            reverse=True
        )[:top_n]
        
        return [(int(item_id), float(score)) for item_id, score in sorted_candidates]
    
    def initialize(self, k_similar: int = 2):
        """初始化推荐系统"""
        df = self.load_data()
        if not df.empty:
            self.build_user_item_index(df)
            self.build_item_similarity_index(df, k_similar)
            return True
        return False


class UserSegmentation:
    """用户分层模块"""
    
    def __init__(self, db: Session):
        self.db = db
    
    def analyze_user(self, user_id: int) -> Dict:
        """分析用户行为，计算用户特征"""
        try:
            # 统计用户行为数
            total_behaviors = self.db.query(func.count(UserBehavior.id)).filter(
                UserBehavior.user_id == user_id
            ).scalar() or 0
            
            # 统计订单数
            total_orders = self.db.query(func.count(Order.id)).filter(
                Order.user_id == user_id,
                Order.status.in_(["paid", "completed"])
            ).scalar() or 0
            
            # 统计总消费
            total_spent = self.db.query(func.sum(Order.total_amount)).filter(
                Order.user_id == user_id,
                Order.status.in_(["paid", "completed"])
            ).scalar() or 0.0
            
            # 计算平均评分
            avg_rating = self.db.query(func.avg(Review.rating)).filter(
                Review.user_id == user_id
            ).scalar() or 0.0
            
            return {
                "user_id": user_id,
                "total_behaviors": total_behaviors,
                "total_orders": total_orders,
                "total_spent": float(total_spent),
                "avg_rating": float(avg_rating)
            }
        
        except Exception as e:
            logger.error(f"分析用户 {user_id} 失败: {e}", exc_info=True)
            return {
                "user_id": user_id,
                "total_behaviors": 0,
                "total_orders": 0,
                "total_spent": 0.0,
                "avg_rating": 0.0
            }
    
    def segment_user(self, user_id: int) -> str:
        """
        对用户进行分层
        
        Returns:
            str: 用户分层（vip, active, normal, inactive）
        """
        features = self.analyze_user(user_id)
        
        # 分层规则
        if features["total_orders"] >= 10 and features["total_spent"] >= 5000:
            segment = "vip"  # 高价值用户
        elif features["total_behaviors"] >= 20 or features["total_orders"] >= 3:
            segment = "active"  # 活跃用户
        elif features["total_behaviors"] >= 5:
            segment = "normal"  # 普通用户
        else:
            segment = "inactive"  # 不活跃用户
        
        # 更新或创建用户分层记录
        user_segment = self.db.query(UserSegment).filter(
            UserSegment.user_id == user_id
        ).first()
        
        if user_segment:
            user_segment.segment = segment
            user_segment.total_behaviors = features["total_behaviors"]
            user_segment.total_orders = features["total_orders"]
            user_segment.total_spent = features["total_spent"]
            user_segment.avg_rating = features["avg_rating"]
            user_segment.updated_at = datetime.now()
        else:
            user_segment = UserSegment(
                user_id=user_id,
                segment=segment,
                total_behaviors=features["total_behaviors"],
                total_orders=features["total_orders"],
                total_spent=features["total_spent"],
                avg_rating=features["avg_rating"]
            )
            self.db.add(user_segment)
        
        try:
            self.db.commit()
            logger.info(f"用户 {user_id} 分层完成: {segment}")
        except Exception as e:
            self.db.rollback()
            logger.error(f"保存用户分层失败: {e}", exc_info=True)
        
        return segment


def collect_user_behavior(
    db: Session,
    user_id: int,
    attraction_id: int,
    behavior_type: str = "view",
    score: Optional[float] = None
):
    """
    收集用户行为数据
    
    Args:
        db: 数据库会话
        user_id: 用户ID
        attraction_id: 景点ID
        behavior_type: 行为类型（view, click, favorite, order, review）
        score: 评分/权重（0-1），如果为None则随机生成
    """
    try:
        # 如果没有提供score，根据行为类型生成
        if score is None:
            if behavior_type == "view":
                score = random.uniform(0.1, 0.3)
            elif behavior_type == "click":
                score = random.uniform(0.3, 0.5)
            elif behavior_type == "favorite":
                score = random.uniform(0.6, 0.8)
            elif behavior_type == "order":
                score = random.uniform(0.8, 1.0)
            elif behavior_type == "review":
                score = random.uniform(0.5, 1.0)
            else:
                score = random.uniform(0.0, 1.0)
        
        # 创建行为记录
        behavior = UserBehavior(
            user_id=user_id,
            attraction_id=attraction_id,
            behavior_type=behavior_type,
            score=score
        )
        
        db.add(behavior)
        db.commit()
        
        logger.info(f"收集用户行为: user_id={user_id}, attraction_id={attraction_id}, type={behavior_type}, score={score}")
        
        return behavior
    
    except Exception as e:
        db.rollback()
        logger.error(f"收集用户行为失败: {e}", exc_info=True)
        raise



